/*
 * 实用工具类
 *
 * - 时间工具：获取时间\格式化时间
 * - 颜色工具：加颜色\消颜色
 * - 文件工具：获取大小\创建删除\读写\路径解析
 * - 代码定位：获取文件名\行号\列号\函数名
 * - 格式化器：c和c++式的字符串格式化
 *
 **/

#pragma once

#include <exception>
#include <iostream>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <memory>
#include <ctime>
#include <cstdint>
#include <easyfs.h>

#if defined (EASYLOG_C_SYTLE_FORMAT)
#   include <cstdarg>
#   if defined (__linux__) || defined (__APPLE__)
#      ifndef _GNU_SOURCE
#      define _GNU_SOURCE
#      endif
#   endif
#elif defined (EASYLOG_CPP20_STD_FORMAT)
#   include <format>
#else
#define FMT_HEADER_ONLY
#   include <fmt/core.h>
#endif
#if defined (__APPLE__) || defined (__linux__)
#   include <execinfo.h>
#   include <signal.h>
#elif defined (WIN32) || defined (WIN64)
#   include <dbghelp.h>
#   include <Windows.h>
#   pragma comment(lib, "DbgHelp.lib")
#endif
#include "except.hpp"

namespace easylog
{
namespace util
{
    struct time
    {
        enum time_enum
        {
            t0sec   =  0,
            t1sec   =  1,            t2sec   =  2,            t3sec   =  3,
            t4sec   =  4,            t5sec   =  5,            t6sec   =  6,
            t7sec   =  7,            t8sec   =  8,            t9sec   =  9,
            t10sec  = 10,            t15sec  = 15,            t20sec  = 20,
            t1min   =  1 * 60,       t2min   =  2 * 60,       t3min   =  3 * 60,
            t4min   =  4 * 60,       t5min   =  5 * 60,       t6min   =  6 * 60,
            t7min   =  7 * 60,       t8min   =  8 * 60,       t9min   =  9 * 60,
            t10min  = 10 * 60,       t15min  = 15 * 60,       t20min  = 20 * 60,
            t1hour  =  1 * 60 * 60,  t2hour  =  2 * 60 * 60,  t3hour  =  3 * 60 * 60,
            t4hour  =  4 * 60 * 60,  t5hour  =  5 * 60 * 60,  t6hour  =  6 * 60 * 60,
            t7hour  =  7 * 60 * 60,  t8hour  =  8 * 60 * 60,  t9hour  =  9 * 60 * 60,
            t10hour = 10 * 60 * 60,  t15hour = 15 * 60 * 60,  t20hour = 20 * 60 * 60,
        };

        static time_t now()
        {
            return std::time(nullptr);
        }

        static std::string format(time_t time, const std::string& fmt)
        {
            struct tm t;
#if defined (_WIN32) || defined (_WIN64)
            localtime_s(&t, &time);
#else
            localtime_r(&time, &t);
#endif
            char buf[32] = {0};
            strftime(buf, 32, fmt.c_str(), &t);

            return buf;
        }
    };

    struct color
    {
        enum color_enum
        {
            none = 0,
            black,
            light_black,
            red,
            light_red,
            green,
            light_green,
            yellow,
            light_yellow,
            blue,
            light_blue,
            purple,
            light_purple,
            cyan,
            light_cyan,
            white,
            light_white,
        };

        static const char* onclr(color_enum cl)
        {
            // Must use int not colors here, because clang will not convert colors to integer,
            // so there is no hash method for colors.
            static std::unordered_map<int, const char*> cols = {
                {         none, "\033[0m"    }, // none
                {        black, "\033[0;30m" }, // black
                {  light_black, "\033[1;30m" }, // dark_gray
                {          red, "\033[0;31m" }, // red
                {    light_red, "\033[1;31m" }, // light_red
                {        green, "\033[0;32m" }, // green
                {  light_green, "\033[1;32m" }, // light_green
                {       yellow, "\033[0;33m" }, // brown
                { light_yellow, "\033[1;33m" }, // yellow
                {         blue, "\033[0;34m" }, // blue
                {   light_blue, "\033[1;34m" }, // light_blue
                {       purple, "\033[0;35m" }, // purple
                { light_purple, "\033[1;35m" }, // light_purple
                {         cyan, "\033[0;36m" }, // cyan
                {   light_cyan, "\033[1;36m" }, // light_cyan
                {        white, "\033[0;37m" }, // light_gray
                {  light_white, "\033[1;37m" }, // white
            };

            if (cols.find(cl) == cols.end())
                throw except("util::color: unknown color");

            return cols[cl];
        }

        static const char* offclr()
        {
            return "\033[0m";
        }
    };

    struct file
    {
        static size_t size(const std::string path)
        {
            return easyfs::file_size(path);
        }

        static void mkdir(const std::string& path)
        {
            easyfs::create_directory(path);
        }

        static void mkfile(const std::string& path)
        {
            easyfs::create_file(path);
        }

        static void remove(const std::string& path)
        {
            easyfs::remove(path);
        }

        static std::string read(const std::string& path)
        {
            return easyfs::read_file(path);
        }

        static void write(std::ofstream& ofs, const std::string& data)
        {
            ofs.write(data.c_str(), data.size());
        }

        static bool exists(const std::string& path)
        {
            return easyfs::exists(path);
        }

        static std::string dirpath(const std::string& path)
        {
            return easyfs::path(path).parent_path().string();
        }
    };

    class source_loc
    {
    public:
        source_loc(const char* file = __builtin_FILE(),
                   const char* func = __builtin_FUNCTION(),
                   const int   line = __builtin_LINE(),
#if defined (__APPLE__) || defined (_WIN32) || defined (_WIN64)
                   const int   colu = __builtin_COLUMN())
#else // gcc can't get column number
                   const uint32_t colu = 0)
#endif
            : file(file), function(func), line(line), column(colu)
        {}

        const char* file;
        const char* function;
        int line;
        int column;
    };

    struct payload
    {
#if defined (EASYLOG_C_SYTLE_FORMAT)
        static std::string format(const char* fmt, ...)
        {
            va_list ap;
            va_start(ap, fmt);

            char* res = nullptr;
            if (vasprintf(&res, fmt, ap) == -1)
            {
                va_end(ap);
                throw except("vasprintf error");
            }
            va_end(ap);

            std::unique_ptr<char> auto_raii(res); // raii will automatic free 'res'
            return res;
        }
#else
        template<typename... Args>
        static std::string format(const std::string& fmt, Args&&... args)
        {
    #if defined (EASYLOG_STD_FORMAT)
            return std::vformat(fmt, std::make_format_args(args...));
    #else
            return fmt::format(fmt, std::forward<Args>(args)...);
    #endif
        }
#endif

#if defined (_WIN32) || defined (_WIN64)
        static int vasprintf(char** strp, const char* fmt, va_list ap)
        {
            int len = _vscprintf(fmt, ap);
            if (len == -1)
                return -1;

            char* res = (char*)malloc(len + 1);
            if (res == nullptr)
                return -1;
            memset(res, 0, len + 1);

            int n = vsnprintf(res, len + 1, fmt, ap);
            if (n == -1)
            {
                free(res);
                return -1;
            }

            *strp = res;
            return n;
        }
#endif
    };

    struct size
    {
        enum size_enum
        {
            m1KB   = 1024,
            m2KB   = m1KB   * 2,
            m5KB   = m1KB   * 5,
            m10KB  = m1KB   * 10,
            m20KB  = m1KB   * 20,
            m50KB  = m10KB  * 5,
            m1MB   = m1KB   * 1024,
            m5MB   = m1MB   * 5,
            m10MB  = m1MB   * 10,
            m20MB  = m1MB   * 20,
            m50MB  = m10MB  * 5,
            m100MB = m10MB  * 10,
            m200MB = m100MB * 2,
            m300MB = m100MB * 3,
            m400MB = m100MB * 4,
            m500MB = m100MB * 5,
            m600MB = m100MB * 6,
            m700MB = m100MB * 7,
            m800MB = m100MB * 8,
            m900MB = m100MB * 9,
            m1GB   = m500MB * 2,
        };

        static size_t tomsgnum(size_t size)
        {
            return size / 128; // the length of is approximately 128 bytes
        }
    };

    struct tracer
    {
#if defined (__APPLE__) || defined (__linux__)
        static std::string stacktrace()
        {

            std::unique_ptr<void*[]> trace(new void*[32]{0});

            int n = backtrace(trace.get(), 32);
            char** msgs = backtrace_symbols(trace.get(), n);

            std::stringstream ss;
            for (int i = 0; i < n; i++)
                 ss << "[stacktrace] " << msgs[i] << '\n';

            free(msgs);
            return ss.str();
        }
#elif defined (_WIN32) || defined (_WIN64)
        static std::string stacktrace()
        {
            HANDLE proc = GetCurrentProcess();
            SymInitialize(proc, NULL, TRUE);

            void* stack[100] = { nullptr };
            unsigned short num = CaptureStackBackTrace(0, 100, stack, NULL);

            std::unique_ptr<SYMBOL_INFO> symbol(new SYMBOL_INFO[sizeof(SYMBOL_INFO) + 256 * sizeof(char)]);
            symbol->MaxNameLen = 255;
            symbol->SizeOfStruct = sizeof(SYMBOL_INFO);

            std::stringstream ss;

            for (int i = 0; i < num; i++)
            {
                SymFromAddr(proc, (DWORD64)(stack[i]), 0, symbol.get());
                ss << "[stacktrace] " << i << ": " << symbol->Name << '\n';
            }

            return ss.str();
        }
#endif
    };

    struct killer
    {
#if defined (__APPLE__) || defined (__linux__)
    static void kill_self()
    {
        raise(9);
    }
#elif defined (WIN32) || defined (WIN64)
    static void kill_self()
    {
        HANDLE handle = GetCurrentProcess();
        TerminateProcess(handle, 0);
        CloseHandle(handle);
    }
#endif
    static void exit(int ret)
    {
        std::exit(ret);
    }
    };
}
    using color_enum = util::color::color_enum;
    using time_enum = util::time::time_enum;
    using size_enum = util::size::size_enum;
}
